import { Injectable } from "@nestjs/common";
import axios, { TypedAxiosInstance } from "restyped-axios";
import {
    API,
    OpenApiConfigBase,
    OpenApiHttpMethod,
    OpenApiRequestBody,
    OpenApiRequestConfig,
    OpenApiPromise,
    OpenApiInterceptors,
} from "@anyshare/openapi";
import { Agent as HttpsAgent } from "https";

interface OpenApiService {
    request<Path extends keyof API, Method extends OpenApiHttpMethod<Path>>(
        config: OpenApiRequestConfig<Path, Method>
    ): OpenApiPromise<Path, Method>;
    get<Path extends keyof API, Method extends OpenApiHttpMethod<Path>>(
        url: Path,
        config?: OpenApiRequestConfig<Path, Method>
    ): OpenApiPromise<Path, Method>;
    head<Path extends keyof API, Method extends OpenApiHttpMethod<Path>>(
        url: Path,
        config?: OpenApiRequestConfig<Path, Method>
    ): OpenApiPromise<Path, Method>;
    delete<Path extends keyof API, Method extends OpenApiHttpMethod<Path>>(
        url: Path,
        config?: OpenApiRequestConfig<Path, Method>
    ): OpenApiPromise<Path, Method>;
    post<Path extends keyof API, Method extends OpenApiHttpMethod<Path>>(
        url: Path,
        data?: OpenApiRequestBody<Path, Method>,
        config?: OpenApiRequestConfig<Path, Method>
    ): OpenApiPromise<Path, Method>;
    put<Path extends keyof API, Method extends OpenApiHttpMethod<Path>>(
        url: Path,
        data?: OpenApiRequestBody<Path, Method>,
        config?: OpenApiRequestConfig<Path, Method>
    ): OpenApiPromise<Path, Method>;
    patch<Path extends keyof API, Method extends OpenApiHttpMethod<Path>>(
        url: Path,
        data?: OpenApiRequestBody<Path, Method>,
        config?: OpenApiRequestConfig<Path, Method>
    ): OpenApiPromise<Path, Method>;
}

function createOpenApiMethod<OpenApiMethod>(
    openApi: OpenApiService,
    args: (keyof OpenApiRequestConfig<any, any>)[],
    currified: OpenApiRequestConfig<any, any> = {}
) {
    return (((...argv: any[]) => {
        const configs: any = {};

        args.forEach((arg, i) => {
            configs[arg] = argv[i];
        });

        return openApi.request({ ...(argv[args.length] || {}), ...configs, ...currified });
    }) as unknown) as OpenApiMethod;
}

export function replaceUrlParams(url: string, params: { [key: string]: string | number }): string {
    return url.replace(/\{\s*([^\{\}]+)\s*\}/g, (_, p) => {
        if (params[p] === undefined) {
            throw new Error(`params["${p}"] is undefined`);
        }
        return params[p] as string;
    });
}

class OpenApiService {
    private _axios: TypedAxiosInstance<API> = axios.create<API>({
        httpsAgent: new HttpsAgent({
            rejectUnauthorized: false,
        }),
    });
    public defaults: OpenApiConfigBase = { https: true, hostname: "localhost" };

    public request<Path extends keyof API, Method extends OpenApiHttpMethod<Path>>(
        config: OpenApiRequestConfig<Path, Method>
    ): OpenApiPromise<Path, Method> {
        const combinedConfigs = { ...this.defaults, ...config };

        const { https, hostname, port, url, method, headers, token, params, query, ...otherConfig } = combinedConfigs;

        const baseURL = `${https ? "https:" : "http:"}//${hostname}${port ? `:${port}` : ""}/api`;

        return (this._axios.request({
            ...otherConfig,
            baseURL,
            url: replaceUrlParams(url as string, params),
            params: query,
            method,
            headers: {
                ...(headers || {}),
                Authorization: `Bearer ${token}`,
            },
        } as any) as unknown) as OpenApiPromise<Path, Method>;
    }

    public get = createOpenApiMethod<OpenApiService["get"]>(this, ["url"], { method: "GET" });
    public head = createOpenApiMethod<OpenApiService["head"]>(this, ["url"], { method: "HEAD" });
    public delete = createOpenApiMethod<OpenApiService["delete"]>(this, ["url"], { method: "DELETE" });
    public post = createOpenApiMethod<OpenApiService["post"]>(this, ["url", "data"], { method: "POST" });
    public put = createOpenApiMethod<OpenApiService["put"]>(this, ["url", "data"], { method: "PUT" });
    public patch = createOpenApiMethod<OpenApiService["patch"]>(this, ["url", "data"], { method: "PATCH" });

    public get interceptors() {
        return this._axios.interceptors as OpenApiInterceptors;
    }
}

@Injectable()
export class ConfigService {
    public authenticationPublicApi = new OpenApiService();
    public deployServiceApi = new OpenApiService();
    public eacpPublicApi = new OpenApiService();
    public hydraPublicApi = axios.create({
        httpsAgent: new HttpsAgent({
            rejectUnauthorized: false,
        }),
    });
    public sharedLinkPublicApi = new OpenApiService();
    public hydraInfo = {
        clientId: "",
        clientSecret: "",
        redirectUri: "https://127.0.0.1:443/anyshare/oauth2/login/callback",
        postLogoutRedirectUri: "https://127.0.0.1:443/anyshare/oauth2/logout/callback",
    };
    public publicInfo = {
        baseUri: "https://127.0.0.1:443",
        hostname: "127.0.0.1",
        https: true,
    };
}
